/*
 * Copyright (C) 2013 Manoel Afonso Filho
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package agimagem.main;

import agimagem.concrete.CrossoverUnitario;
import agimagem.concrete.ImgInicial;
import agimagem.concrete.ImgReplacement;
import agimagem.concrete.TorneioBinario;
import agimagem.concrete.MutacaoIV;
import agimagem.context.AG;
import agimagem.context.ImgAG;
import agimagem.populacao.Individuo;
import agimagem.populacao.Populacao;
import agimagem.util.AGChave;
import agimagem.util.Util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Classe principal do sistema responsável pela configuração do AG, leitura e
 * escrita de arquivos.
 * @author Manoel Afonso Filho
 */
public class Main {

    private static Logger log = LogManager.getLogger(Main.class.getName());
    public static final String ARQUIVO_ORIGINAL = "Arquivos/sample.wav";
    public static final String ARQUIVO_CRIPTOGRAFADO = "Arquivos/ag_cripto.wav";
    public static final String ARQUIVO_DECRIPTOGRAFADO = "Arquivos/ag_decripto.wav";

    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        byte[] bytesImg = Util.lerBytes(ARQUIVO_ORIGINAL);

        //LER UMA IMAGEM
//        Util.lerImagem(ARQUIVO_ORIGINAL);
//        byte[] bytesImg = Util.Imagem.px;

        System.out.println("Criptografando arquivo \"" + ARQUIVO_ORIGINAL +"\" (" + bytesImg.length + " bytes)");

        //Inicializa o gerador de números aleatórios
        Util.iniciarSecureRandom();

        // Configuração do AG e de seus operadores.
        ImgAG ag = new ImgAG();
        ag.setInicializacao(new ImgInicial());
        ag.setSelecao(new TorneioBinario());
        ag.setCrossover(new CrossoverUnitario());
        ag.setMutacao(new MutacaoIV());
        ag.setReplacement(new ImgReplacement());

        AG.setNumGeracoes(50);

        System.out.println("Configurações do AG:");
        System.out.println(ag);

        long ini = System.nanoTime();

        // População inicial
        Populacao popIni = ag.gerarPopulacaoInicial(bytesImg);

        double fitnessInicial = popIni.getFittest().getFitness(); // Melhor fitness inicial.

        // Execução das gerações
        for (int i = 0; i < AG.getNumGeracoes(); i++) {
            ag.executarGeracao(popIni);
        }

        long dt = System.nanoTime() - ini;

        // Calcula a evolução do fitness
        Individuo fittest = popIni.getFittest();

        double fitnessFinal = fittest.getFitness();
        aumentoFitness(fitnessInicial, fitnessFinal);

        System.out.println("Tempo de execução: " + dt + " ns");

        // Grava o arquivo criptografado final
        System.out.println("Gravando arquivo criptografado em \"" + ARQUIVO_CRIPTOGRAFADO + "\" (" + fittest.getCiphertext().length + " bytes)");
        Util.escreverBytes(ARQUIVO_CRIPTOGRAFADO, fittest.getCiphertext());

        //Gravar Imagem
//        System.arraycopy(fittest.getCiphertext(), 0, Util.Imagem.px, 0, Util.Imagem.px.length);
//        Util.saveImage(Util.Imagem.bufferedImage, ARQUIVO_CRIPTOGRAFADO);

        //TODO Gravar o binário da chave.


        //DECRIPTOGRAFIA
        byte[] cipherText = fittest.getCiphertext();
        for (int i = AG.getNumGeracoes() - 1; i >= 0; i--) {
            AGChave chave = fittest.getChaveDaGeracaoNull(i);
            if(chave != null){
                cipherText = ag.decriptografar(cipherText, chave);
            }
        }

    }

    public static void aumentoFitness(double ini, double fim){
        double dif = fim - ini; // O quanto aumentou.
        double p = (dif * 100) / ini;
        System.out.println("Aumento do fitness:");
        System.out.println(ini + " --> " + fim);
        System.out.println("(Aumento de " + p + "%)");
    }

}
